Fire680x0 has been joined by FirePPC and FirePPC/FPU. Nothing has been changed about the 680x0 version, except removing a unneccesary routine (get_CLUT). The only thing changed about the DOC is my promise to buy a beer to anyone who made a PPC version, I'll buy myself one instead (or perhaps several).
Introduction
This is just a short(ish) doc to explain, or at least give an example, as to how you make fire. Obviously it’s not of the “rub two sticks together”-kind. It’s really just fire-coloured pixels moving in a vaguely interesting way. But that’s nice too, yeah?
This source is DEFINITELY not an example of nice programming. It’s quick and dirty, and is just meant to be an example of implementing an idea into assembler. As an added bonus this is also my very first assembler program (on the Mac, that is), so be sure to keep it guys: it’s gonna be a collectors item (or you could, at the very least, blackmail me in years to come).
Some of the text and demo was written while under the influence of a particularly nasty hangover, caused by ... well, I’m not sure, but it seemed to be a clear liquid. And I’m sure I spotted a slug in the bottle. Anyway, just so you know that any kind of weird English, weird code or weird explanations might not have occurred if I was feeling my old self. Well, it probably would, but you never know.
As you might notice, the skeleton of Fire is really just the Graphics Demo which came with Fantasm. I suppose that’s what it’s there for, but thanks to those Lightsoft guys anyway.
Well, that's enough excuses ...
Theory
Right! Let’s give it a try. If there is something you don’t understand, check the code.
Let’s start with the colours. As we’re making fire it would seem logical to have a palette which contains all the fire colours. In this case we’ll just use red. This colour should be colour number 255 in the CLUT and black should be colour 0. The in between colours are the shades from black to red. With me so far? Good.
We’ll need two buffers. Call them SCREEN and SPARE. SCREEN and SPARE should not be thought of as exact copies of the screen. The buffers only need to hold a byte for each pixel, the byte representing a colour number in the CLUT (0-255 or 0-$ff if you will). The smart thing about all this, is that since we fade from black to red, the colour number corresponds with the colourintensity of each colour. Like colour number 0 is black (3*$0000) and colour number $ff is red (R=$ffff).
Now we need to use a random-routine. Use the one supplied with Fantasm.
For the first 2*DIM_X bytes in SCREEN, randomly set each byte to be either $00 or $ff. 2*DIM_X is the same as the two first lines (rows of pixels) in your buffer. $00 and $ff represents colournumbers in the CLUT. We do this to create the chaotic nature of a fire. If we didn't do it, we'd just have a red-fading skyline.
After doing this we start to calculate the fire. It's very easy. For every byte in SCREEN we need to calculate the average value of three of the surrounding pixels. If we call a specific byte's position (X,Y) we get the bytes at (X-1,Y+1), (X,Y+1) and (X,Y+2). Add these three values together, and divide by 3 to get the average (obviously).
Here’s some ASCII to explain it a bit better:
X
12
3
Where X is the current colour (byte) being checked, and 1-3 are the positions of the colours you use for getting the average value. As you might have guessed, this means you can't get the average of the first two lines (the ones who are filled with random numbers), because it would mean that we would get bytes from outside the buffer. So we skip the first two lines (2*DIM_X). It doesn't really matter, because we don't plot them anyway (look in Tech Notes).
Remember I said that the bytes represented a colour-number? Good, because we subtract 1 from the average value. We do this to make the fire fade out. If it didn't fade it wouldn't look much like fire. What we actually do, is substracting 1 from the colour number and thus end up with a colour-number nearer 0, and since colour-numbers and colour-intensity are basically the same, the colour will fade towards black. Okay?
Okay, put this value (3-colour average -1) into SPARE at the same position as the original in SCREEN: (X,Y). Then we move on to the next byte and do the same.
After doing the complete buffer, and thus creating new data in the SPARE-buffer, we plot the SPARE-buffer. PRESTO! We have frame one. SPARE now represents what we see on the screen and we need to switch SCREEN with SPARE. After switching SCREEN with SPARE we begin from the top, now using the new data in SPARE to create a new screen in SCREEN. Phew!
IMPORTANT: To make it easier on myself, the buffers are actually upside down. It’s a waste of time and space to jump to end of a buffer to plot the random colours and calculate averages. So whenever I do something to the buffers in the demo, remember that they are upside down. I just plot them with the right way up. Yeah?
Is this gibberish? I’m not too good at explaining things, but after you’ve read this you should read the source. I’ve tried to make it clear.
Tech notes
When dealing with graphics it usually comes down to speed. And where are the critical areas?
Well, obviously we need to plot a lot of pixels. At first I used the library-routine PLOT_256, but that was not optimal.
As it takes quite a lot of time to calculate the average of the surrounding pixels, it would be too slow to fill the entire screen with pixels. First thing we do, is to plot 16 pixels(4*4) for every 1. Since the colours fade rather smoothly it's not that bad. You can see it's blocky, but it still looks pretty.
This reduced the number of average values to calculate, but put a strain on the plotting. So I did three things:
1) I sneaked out the PLOT_256 from the library source, thus avoiding a branch (not much...but still). We also need to change the PLOT so it's necessary to take it out of the library anyway.
2) We need one plot of 16 pixels (4*4). We can plot 4 pixels in one MOVE by plotting a long word instead of a byte.
3) After plotting the first row of 4 pixels we know that the next four are always directly beneath. So instead of checking with the Y_TAB table, we just add the screen width to get to the next row.
These three stages increases the speed significantly. Try using PLOT_256 instead of the customized version. It will slow the fire down a lot.
I hope Stuart and Rob wont mind I've butchered their routine like this ;-)
I’m not sure the speed you will gain by checking if a pixel already is the colour it needs to be in the next frame, is worth it. I’m not even sure that that many pixels are the same colour two frames in a row. It’s probably not worth the effort, but try it out.
Another way to speed up the demo is to use fewer pixels when getting the average. The fewer pixels, the faster (duh). Try using a different number of surrounding pixels. This might need some thought. For example, if you just use the three pixels directly below your current pixel, the fire won’t be nice at all. Only a few combinations work.
Starting out with this little project I used all 8 surrounding pixels which was too slow, and not that pretty actually. I’ve also used 5 and 4. This is, of course, slower than 3, since you need to check more values. Looks a bit different, though. I suppose it’s a matter of taste. I don’t think it’s possible to achieve a nice effect with only 2 pixels. I couldn’t, anyway. In retrospect I think you get a slightly nicer fire if you use a 4-colour average, but I got caught up in making the fire run fullscreen and I sacrified some of the cosmetics. It's still okay, though.
And yet another way is to make DIM_X smaller and the pixels larger. The pixels are quite big already, though, so you’d better put this on a low priority. But if you switch to a lower resolution, you'll need to make both DIM_X and DIM_Y smaller.
Is Pseudo-random faster than Random? I don't know. It might be worth a go. Speaking of random: how random are those numbers really? It seemed to me that the first number it picked was alway the same?
I also tried to replace the random routine with a table. Instead of calculating 2*DIM_X random numbers each frame, I put a few hundred in a table, and used them instead. Speed improvement? None that I could detect. Of course it must be faster. But not fast enough to be noticeable ... not by me, anyway. In the end I removed the table, because it needed a large(ish) amount of space if the fire shouldn't look like it was looping. Makes the source smaller and simpler.
I've ALSO tried to make a division-table (replacing the DIVS #3,d5). Again: Not noticeable. And finally I tried out a muls-table (replacing MULS #$1010101,d0). Nothing. I admit I didn't expect any huge speed-gain, but ... I suppose we takling milliseconds and stuff like that. Still, if you need some speed this is still one of the ways to do it. Thousands of MOVEs should be faster than thousands of MULS. Not by much, but every little thing counts (or am I still not used to a fast computer?). I kept DIVS and MULS instead of the tables, as it makes the source easier to understand.
I also tried replacing MULS #$1010101,d0 with some shifts instead. I replaced the MULS with three shifts and a couple of adds, but that just slowed the whole thing down. I suppose I'm overreacting when it comes to the time it takes to do MULS and DIVS, but it's a force of habit.
There is no need to plot a lot of black pixels after the fire has faded out. If you alter the program so it doesn't plot black pixels, it will be faster.
Code notes
Any bad habits in the code is purely a result of spending to much time in front of an Amiga. Although I don't miss it much, I do miss the prices. £200+ for Codewarrior. £200+ for a paint-program. Mac-software is very, very expensive ... except, thank God, for PowerFantasm and, dare I say, Fanta_C?
My startup-routine is pretty pathetic. I suppose I could set up a window, but that’s so ... so ... systemfriendly. That’s the way it is with systems: Easy, Fast, Good: pick two ... or in case you're using Windows 95: none.
Because I run MacOS in 800*600, this resolution has been sort of hardwired into the demo. It's easy enough to change. But remember that Fire will NOT run on 640*480 before you change the source!
In a desperate attempt to avoid using resources in an otherwise simple demo, I calculate the CLUT and load it straight in ... also I have been unable to find a program that let’s me create a CLUT. You DON’T want to know the grief I had making the CLUT work. Luckily my plea for help was answered by Rob, who explained how _SetEntries work.
There might be a bug hiding somewhere in the program. Is it me, or does it look like some of the colour don't move/fade? Especially near the top.
Final notes
So, that was fire. Pretty nice, although not exactly mind blowing. Do with it what you want. Except saying you made it (although if you really want to take credit for this code, you’re so messed up you probably wont think twice about violating a copyright notice).
This demo was developed on a Macintosh Performa 5320 with 16MB RAM. As you might know 5320 runs at 120Mhz, and I can only say that FIRE ran at a decent enough speed on my machine.
I appreciate comments in all shapes and forms. Although “your demo is crap” certainly may be correct, it’s not all that helpful: try to be constructive (not that I think you nice people would say that). General tips and hints will be especially welcome. Tell me the ‘smart’ way of blatting sprites on screen (icl8s seems limiting). Tell me the smart way to make a 256-colour tile-scroller. I have NO idea how to put pictures on screen. Do you just open a PICT resource and copy it in? Source for soundtracker-player anyone? Maybe I’ll give you a good idea in return (until I learn a bit more about my Mac these good ideas may be restricted to: Don’t wear plaid).
The demos is released “as-is”. I make no promises that it’ll work on anything but my own machine. If you have some weird expansion connected to your Mac (like a mouse) who knows what’ll happen. I’m pretty certain it wont delete your hard drive, though. In any case: use it at your own risk (sure beats the page long disclaimers we usually see in Shareware programs).
Big hello to all PowerFantasm’s users. Coding in assembler, eh? It's the computer equivalent of riding a Harley Davidson.
And of course a big pat on the back to all at Lightsoft for making an extremely nice program. One day they’ll be writing songs about you ... no, really! ... now, what rhymes with Lightsoft?
As I said: Comments, suggestions, improvements, idea-bouncing, small-talk? Write to me, why don’t you? I’ll listen to anything. Except The Kelly Family ... somebody shoot them.
E-mail:
2811aj@fiol.brock.dk
or
maccoder@geocities.com
Look out for my homepage, which will be up and running real soon. I plan to fill it with lot's of exciting programming stuff or, failing that, just programming stuff. In any case, most of the things I make (various routines) will be posted there for you to download.
It can be found at: http://www.geocities.com/SiliconValley/4889/
I expect it to be usable at the end of november/start of december. Contributions are accepted with pleasure.
Erm, bye
Allan Jensen (wannabe programmer and pretty nice guy)
P.S. Have you ever noticed that when you're programming and you look at the watch, it's always 3 o'clock in the morning? And when you drink some of the coffee standing next to you, it's always cold? I guess it just goes to show that while Einstein is a good man to know when you want to know something about relativity, who you really want to send for is a programmer who knows far, far more about time and space that any ordinary man could or should.